
#Region "Using Directives"

Imports System
Imports System.Collections.Generic
Imports System.Text
Imports C4F.DevKit.Bluetooth.ObjectExchange

#End Region

Namespace C4F.DevKit.Bluetooth.ObjectPushProfile

    ''' <summary>
    ''' Implements Object Push Profile.
    ''' </summary>
    Public Class BluetoothOPPManager
        Inherits ComponentModel.Component

#Region "Private Fields"

        ''' <summary>
        ''' NetworkStream used for communicating with a remote device.
        ''' </summary>
        Private m_networkStream As Net.Sockets.NetworkStream

        ''' <summary>
        ''' TypeVerifier may be used for type verification.
        ''' </summary>
        Private m_typeVerifier As ObjectTypeVerifier

        ''' <summary>
        ''' Maximum packet size to send over the network stream.
        ''' </summary>
        Private maxPacketSize As UShort

        ''' <summary>
        ''' The queue of pending asynchronous OPP requests.
        ''' </summary>
        Private requestQueue As List(Of OPPRequest)

        ''' <summary>
        ''' A background worker that handles asynchronous OPP requests.
        ''' </summary>
        Private asyncOperationHandler As ComponentModel.BackgroundWorker

        ''' <summary>
        ''' The asynchronous OPP request that is being handled by 
        ''' asyncOperationHandler.
        ''' </summary>
        Private runningAsyncRequest As OPPRequest

#End Region

#Region "Private Constants"

        ''' <summary>
        ''' Default maximum packet size to send over the network stream.
        ''' </summary>
        Private Const DEFAULT_MAX_PACKET_SIZE As UShort = 8192

#End Region

#Region "Public Properties"

        ''' <summary>
        ''' Gets or sets the NetworkStream used for communicating with a remote device.
        ''' </summary>
        <ComponentModel.Browsable(False)> _
        Public Property NetworkStream() As Net.Sockets.NetworkStream

            Get
                Return Me.m_networkStream
            End Get

            Set(ByVal value As Net.Sockets.NetworkStream)
                Me.m_networkStream = value
            End Set

        End Property

        ''' <summary>
        ''' This object needs to be set in order to make use of the type 
        ''' verification features. It is required that the value for this 
        ''' property be set with an instance of a class deriving from the 
        ''' abstract class ObjectTypeVerifier with appropriate implementation
        ''' for the overrideable method VerifyType(). If the type verifier 
        ''' is set, it may be used after every Get operation. This property 
        ''' should be set to null if the type verifier is not to be used.
        ''' </summary>
        <ComponentModel.Browsable(False)> _
        Public Property TypeVerifier() As ObjectTypeVerifier

            Get
                Return Me.m_typeVerifier
            End Get

            Set(ByVal value As ObjectTypeVerifier)
                Me.m_typeVerifier = value
            End Set

        End Property

#End Region

#Region "Events and Delegates"

        ''' <summary>
        ''' The event handler for an ObjectPushBegin event.
        ''' </summary>
        ''' <param name="sender">The sender of the event.</param>
        ''' <param name="e">EventArgs when an Object Push operation begins.
        ''' </param>
        Public Delegate Sub ObjectPushBeginEventHandler(ByVal sender As Object, ByVal e As ObjectPushBeginEventArgs)

        ''' <summary>
        ''' This event is fired when an Object Push operation begins.
        ''' </summary>
        Public Event ObjectPushBegin As ObjectPushBeginEventHandler

        ''' <summary>
        ''' The event handler for an ObjectPushCompleted event.
        ''' </summary>
        ''' <param name="sender">The sender of the event.</param>
        ''' <param name="e">EventArgs when an Object Push operation completes.
        ''' </param>
        Public Delegate Sub ObjectPushCompletedEventHandler(ByVal sender As Object, ByVal e As ObjectPushCompletedEventArgs)

        ''' <summary>
        ''' This event is fired when an Object Push operation completes.
        ''' </summary>
        Public Event ObjectPushCompleted As ObjectPushCompletedEventHandler

        ''' <summary>
        ''' The event handler for a BusinessCardPullBegin event.
        ''' </summary>
        ''' <param name="sender">The sender of the event.</param>
        ''' <param name="e">EventArgs when a Business Card Pull operation 
        ''' begins.</param>
        Public Delegate Sub BusinessCardPullBeginEventHandler(ByVal sender As Object, ByVal e As BusinessCardPullBeginEventArgs)

        ''' <summary>
        ''' This event is fired when a Business Card Pull operation begins.
        ''' </summary>
        Public Event BusinessCardPullBegin As BusinessCardPullBeginEventHandler

        ''' <summary>
        ''' The event handler for a BusinessCardPullCompleted event.
        ''' </summary>
        ''' <param name="sender">The sender of the event.</param>
        ''' <param name="e">EventArgs when a Business Card Pull operation 
        ''' completes.</param>
        Public Delegate Sub BusinessCardPullCompletedEventHandler(ByVal sender As Object, _
                                                                   ByVal e As BusinessCardPullCompletedEventArgs)

        ''' <summary>
        ''' This event is fired when a Business Card Pull operation completes.
        ''' </summary>
        Public Event BusinessCardPullCompleted As BusinessCardPullCompletedEventHandler

        ''' <summary>
        ''' The event handler for a BusinessCardExchangeBegin event.
        ''' </summary>
        ''' <param name="sender">The sender of the event.</param>
        ''' <param name="e">EventArgs when a Business Card Exchange operation
        ''' begins.</param>
        Public Delegate Sub BusinessCardExchangeBeginEventHandler(ByVal sender As Object, _
                                                                   ByVal e As BusinessCardExchangeBeginEventArgs)

        ''' <summary>
        ''' This event is fired when a Business Card Exchange operation begins.
        ''' </summary>
        Public Event BusinessCardExchangeBegin As BusinessCardExchangeBeginEventHandler

        ''' <summary>
        ''' The event handler for a BusinessCardExchangeCompleted event.
        ''' </summary>
        ''' <param name="sender">The sender of the event.</param>
        ''' <param name="e">EventArgs when a Business Card Exchange operation
        ''' completes.</param>
        Public Delegate Sub BusinessCardExchangeCompletedEventHandler(ByVal sender As Object, _
                                                                       ByVal e As BusinessCardExchangeCompletedEventArgs)

        ''' <summary>
        ''' This event is fired when a Business Card Exchange operation 
        ''' completes.
        ''' </summary>
        Public Event BusinessCardExchangeCompleted As BusinessCardExchangeCompletedEventHandler

#End Region

#Region "Constructors"

        ''' <summary>
        ''' Initializes the OPPManager object.
        ''' </summary>
        Public Sub New()

            ' Initialize request queue and background worker.
            InitializeOPPManager()

        End Sub

        ''' <summary>
        ''' Initializes the OPPManager object.
        ''' </summary>
        ''' <param name="networkStream">Network stream over which data has to 
        ''' be transferred.</param>
        ''' <exception cref="ArgumentNullException">NetworkStream is passed as
        ''' a null reference.</exception>
        Public Sub New(ByVal networkStream As Net.Sockets.NetworkStream)

            If networkStream Is Nothing Then
                Throw New ArgumentNullException("networkStream")
            End If

            ' Set the network stream.
            Me.m_networkStream = networkStream

            ' Initialize request queue and background worker.
            InitializeOPPManager()

        End Sub

#End Region

#Region "Private Functions/Methods"

        ''' <summary>
        ''' Initializes OPPManager object.
        ''' </summary>
        Private Sub InitializeOPPManager()

            With Me

                ' Initialize the list used to maintain the pending asynchronous requests.
                .requestQueue = New List(Of OPPRequest)()

                ' Initialize the background worker.
                .asyncOperationHandler = New ComponentModel.BackgroundWorker()

                AddHandler .asyncOperationHandler.DoWork, AddressOf asyncOperationHandler_DoWork

                AddHandler .asyncOperationHandler.RunWorkerCompleted, AddressOf asyncOperationHandler_RunWorkerCompleted

                .asyncOperationHandler.WorkerSupportsCancellation = True

            End With

        End Sub

        ''' <summary>
        ''' Adds an asynchronous OPP request to the Request Queue.
        ''' </summary>
        ''' <param name="request">The asynchronous OPP request to be added to
        ''' the Request Queue.</param>
        Private Sub AddRequestToRequestQueue(ByVal request As OPPRequest)

            With Me

                ' Add the request to the list of pending requests.
                Threading.Monitor.Enter(.requestQueue)

                .requestQueue.Add(request)
                Threading.Monitor.Exit(.requestQueue)

                ' If the background worker handling asynchronous operations is not
                ' busy, put it to work.
                If Not .asyncOperationHandler.IsBusy Then
                    .asyncOperationHandler.RunWorkerAsync()
                End If

            End With

        End Sub

        ''' <summary>
        ''' Event handler for background worker's DoWork event. This method 
        ''' executes an asynchronous operation from the request queue. It also 
        ''' raises the asynchronous operation's Begin if implemented.
        ''' </summary>
        ''' <param name="sender">The background worker that will process this 
        ''' method.</param>
        ''' <param name="e">DoWorkEventArgs to store the outcome of the 
        ''' operation.</param>
        Private Sub asyncOperationHandler_DoWork(ByVal sender As Object, ByVal e As ComponentModel.DoWorkEventArgs)

            With Me

                Threading.Monitor.Enter(.requestQueue)

                Try

                    If .requestQueue.Count <> 0 Then

                        ' There are pending asynchronous requests in the request queue.
                        ' Get the 1st request from the list of pending asynchronous operations.       
                        .runningAsyncRequest = .requestQueue(0)

                        ' Remove it from the list.
                        .requestQueue.RemoveAt(0)

                    Else

                        ' No work to be done.
                        Return

                    End If

                Finally

                    Threading.Monitor.Exit(.requestQueue)

                End Try

                ' If the request was cancelled, set e.Cancel to true.
                If .asyncOperationHandler.CancellationPending Then

                    e.Cancel = True
                    Return

                End If

                ' Get the parameters of the request.
                Dim requestParameters() As Object = .runningAsyncRequest.RequestParameters

                ' Raise the appropriate begin event and perform the operation.
                Select Case .runningAsyncRequest.RequestType

                    Case OPPRequestType.ObjectPush

                        ' Raise the Object Push Begin event if required.
                        RaiseEvent ObjectPushBegin(Me, New ObjectPushBeginEventArgs(requestParameters(0).ToString(), _
                                                                                    .runningAsyncRequest.UserState))

                        Try

                            ' Lock the network stream.
                            Threading.Monitor.Enter(.m_networkStream)

                            ' Perform the operation.
                            ' No result is to be returned.
                            PushObjectToOPPService(requestParameters(0).ToString(), requestParameters(1))

                        Catch ex As Exception

                            e.Result = New OPPException(OPPResources.Error_ObjectPush, ex)

                        Finally

                            ' Release the network stream.
                            Threading.Monitor.Exit(.m_networkStream)

                        End Try

                        Exit Select

                    Case OPPRequestType.BusinessCardPull, OPPRequestType.BusinessCardPullWithVerification

                        ' Raise the Business Card Begin event if required.
                        RaiseEvent BusinessCardPullBegin(Me, New BusinessCardPullBeginEventArgs(.runningAsyncRequest.UserState))

                        ' Perform the operation.
                        Try

                            ' Lock the network stream.
                            Threading.Monitor.Enter(.m_networkStream)

                            Dim cardName As String = String.Empty
                            Dim cardContents(0) As Byte

                            PullBusinessCardFromOPPService(cardName, cardContents, requestParameters(0).ToString())

                            ' Set the return parameters to e.Result.
                            e.Result = New Object() {cardName, cardContents}

                        Catch ex As Exception

                            e.Result = New OPPException(OPPResources.Error_BusinessCardPull, ex)

                        Finally

                            ' Release the network stream.
                            Threading.Monitor.Exit(.m_networkStream)

                        End Try

                        Exit Select

                    Case OPPRequestType.BusinessCardExchange, OPPRequestType.BusinessCardExchangeWithVerification

                        ' Raise the Business Card Exchange event if required.
                        RaiseEvent BusinessCardExchangeBegin(Me, _
                                                             New BusinessCardExchangeBeginEventArgs(requestParameters(0).ToString(), _
                                                                  .runningAsyncRequest.UserState))

                        ' Perform the operation.
                        Try

                            ' Lock the network stream.
                            Threading.Monitor.Enter(.m_networkStream)

                            Dim cardName As String = String.Empty
                            Dim cardContents(0) As Byte

                            ExchangeBusinessCardsWithOPPService(requestParameters(0).ToString(), requestParameters(1), _
                                                                cardName, cardContents, requestParameters(2).ToString())

                            ' Set the return parameters to e.Result.
                            e.Result = New Object() {cardName, cardContents}

                        Catch ex As Exception

                            e.Result = New OPPException(OPPResources.Error_BusinessCardExchange, ex)

                        Finally

                            ' Release the network stream.
                            Threading.Monitor.Exit(.m_networkStream)

                        End Try

                        Exit Select
                End Select
            End With
        End Sub

        ''' <summary>
        ''' Event handler for background worker's RunWorkerCompleted event. 
        ''' This method packs the outcome of an asynchronous operation into the
        ''' appropriate EventArgs and raises the operation's Completed event if
        ''' implemented. It then puts the background worker to work on any 
        ''' pending asynchronous requests from the request queue.
        ''' </summary>
        ''' <param name="sender">The background worker that will process this
        ''' method.</param>
        ''' <param name="e">RunWorkerCompletedEventArgs which stores the outcome
        ''' of the operation.</param>
        Private Sub asyncOperationHandler_RunWorkerCompleted(ByVal sender As Object, _
                                                              ByVal e As ComponentModel.RunWorkerCompletedEventArgs)

            With Me

                ' If there is no current request, exit.
                If .runningAsyncRequest IsNot Nothing Then

                    ' Get the parameters of the request.
                    Dim requestParameters() As Object = .runningAsyncRequest.RequestParameters

                    Dim operationException As Exception = Nothing

                    ' Raise the appropriate completed event.
                    Select Case .runningAsyncRequest.RequestType

                        Case OPPRequestType.ObjectPush

                            ' If Me.ObjectPushCompleted IsNot Nothing Then
                            If Not e.Cancelled AndAlso e.Result IsNot Nothing AndAlso (TypeOf e.Result Is Exception) Then

                                ' If the operation was not cancelled 
                                ' and there is some exception.
                                operationException = CType(e.Result, Exception)

                            End If

                            ' Raise the event.
                            RaiseEvent ObjectPushCompleted(Me, New ObjectPushCompletedEventArgs(requestParameters(0).ToString(), _
                                                                                                operationException, e.Cancelled, _
                                                                                                .runningAsyncRequest.UserState))

                            Exit Select

                        Case OPPRequestType.BusinessCardPull, OPPRequestType.BusinessCardPullWithVerification

                            ' If Me.BusinessCardPullCompleted IsNot Nothing Then
                            ' Get the output parameters of the operation.
                            Dim cardName As String = String.Empty

                            Dim cardContents() As Byte = New Byte(-1) {}

                            If Not e.Cancelled AndAlso e.Result IsNot Nothing Then

                                ' If the operation was not cancelled and 
                                ' there is some result.
                                If TypeOf e.Result Is Exception Then
                                    operationException = CType(e.Result, Exception)
                                Else

                                    ' Exception occurred.
                                    ' Operation was successful.
                                    Dim outputParameters() As Object = TryCast(e.Result, Object())

                                    cardName = Convert.ToString(outputParameters(0))
                                    cardContents = TryCast(outputParameters(1), Byte())

                                End If

                            End If

                            ' Raise the event.
                            RaiseEvent BusinessCardPullCompleted(Me, New BusinessCardPullCompletedEventArgs(cardName, _
                                                                                                cardContents, _
                                                                                                operationException, _
                                                                                                e.Cancelled, _
                                                                                                .runningAsyncRequest.UserState))

                            Exit Select

                        Case OPPRequestType.BusinessCardExchange, OPPRequestType.BusinessCardExchangeWithVerification

                            ' If Me.BusinessCardExchangeCompleted IsNot Nothing Then
                            ' Get the output parameters of the operation.
                            Dim cardName As String = String.Empty

                            Dim cardContents() As Byte = New Byte(-1) {}

                            If Not e.Cancelled AndAlso e.Result IsNot Nothing Then

                                ' If the operation was not cancelled and there is some result.
                                If TypeOf e.Result Is Exception Then

                                    operationException = CType(e.Result, Exception)

                                Else

                                    ' Exception occurred.
                                    ' Operation was successful.
                                    Dim outputParameters() As Object = TryCast(e.Result, Object())

                                    cardName = Convert.ToString(outputParameters(0))
                                    cardContents = TryCast(outputParameters(1), Byte())

                                End If

                            End If

                            ' Raise the event.
                            RaiseEvent BusinessCardExchangeCompleted(Me, _
                                        New BusinessCardExchangeCompletedEventArgs(requestParameters(0).ToString(), _
                                        cardName, cardContents, operationException, e.Cancelled, .runningAsyncRequest.UserState))

                            Exit Select

                    End Select

                End If

                ' If there are pending asynchronous requests, set the background
                ' worker to work on them.
                If .requestQueue.Count > 0 Then
                    .asyncOperationHandler.RunWorkerAsync()
                End If

            End With

        End Sub

        ''' <summary>
        ''' Pushes data over the stream to the remote OPP Service.
        ''' </summary>
        ''' <param name="localObjectName">Name of the object to be pushed to
        ''' the remote device.</param>
        ''' <param name="localObjectContents">The object to be pushed.</param>
        ''' <exception cref="ObjectExchangeException">The remote service denies
        ''' the push request or an error occurs during the push operation or an
        ''' attempt to access the network stream fails.</exception>
        ''' <exception cref="InvalidOperationException">networkStream is not set.</exception>
        Private Sub PushObjectToOPPService(ByVal localObjectName As String, ByVal localObjectContents() As Byte)

            If Me.m_networkStream Is Nothing Then
                Throw New InvalidOperationException(OPPResources.Error_NetworkStreamNotSet)
            End If

            Dim localObjectStream As New IO.MemoryStream(localObjectContents)
            Dim objectReader As New IO.BinaryReader(localObjectStream)

            ' List of Put requests to be sent.
            Dim putRequestPackets As New List(Of ObjectExchangeItem)()

            Dim packet As New ObjectExchangeItem(ObjectExchangeOpcode.Put)

            ' Add the Name and Length headers to the 1st Put packet.
            packet.AddHeader(ObjectExchangeHeaderType.Name, localObjectName)
            packet.AddHeader(ObjectExchangeHeaderType.Length, _
                             BitConverter.GetBytes(ObjectExchangeHelper.FormatUInt32(Convert.ToUInt32(localObjectContents.Length))))

            Dim noOfObjectBytesLeftToRead As Long = localObjectStream.Length

            If (packet.PacketSize + noOfObjectBytesLeftToRead + 3) > maxPacketSize Then

                ' Object is large. So split the Object into packets.

                ' 1st packet.
                Dim noOfObjectBytesToRead As Integer = maxPacketSize - packet.PacketSize - 3

                packet.AddHeader(ObjectExchangeHeaderType.Body, objectReader.ReadBytes(noOfObjectBytesToRead))
                noOfObjectBytesLeftToRead -= noOfObjectBytesToRead
                putRequestPackets.Add(packet)

                ' Rest of the packets.
                While noOfObjectBytesLeftToRead > 0

                    packet = New ObjectExchangeItem(ObjectExchangeOpcode.Put)

                    If noOfObjectBytesLeftToRead > (maxPacketSize - packet.PacketSize - 3) Then

                        ' Intermediate packet.
                        noOfObjectBytesToRead = maxPacketSize - packet.PacketSize - 3
                        packet.AddHeader(ObjectExchangeHeaderType.Body, objectReader.ReadBytes(noOfObjectBytesToRead))

                    Else

                        ' Last packet.
                        packet.Opcode = ObjectExchangeOpcode.PutFinal

                        noOfObjectBytesToRead = Convert.ToInt32(noOfObjectBytesLeftToRead)
                        packet.AddHeader(ObjectExchangeHeaderType.EndOfBody, objectReader.ReadBytes(noOfObjectBytesToRead))
                    End If

                    noOfObjectBytesLeftToRead -= noOfObjectBytesToRead
                    putRequestPackets.Add(packet)

                End While

            Else

                ' Object fits in 1 packet.
                packet.Opcode = ObjectExchangeOpcode.PutFinal

                packet.AddHeader(ObjectExchangeHeaderType.EndOfBody, objectReader.ReadBytes(Convert.ToInt32(noOfObjectBytesLeftToRead)))
                putRequestPackets.Add(packet)

            End If

            For packetNo As Integer = 0 To putRequestPackets.Count - 1

                ' Send the Put request(s).

                Dim putRequestBytes() As Byte = putRequestPackets(packetNo).GetByteStream()
                Dim putResponseStream As New IO.MemoryStream()

                Try

                    ' Send the Put request.
                    Me.m_networkStream.Write(putRequestBytes, 0, putRequestBytes.Length)

                    ' Wait for a response.
                    While Not Me.m_networkStream.DataAvailable

                        Continue While

                    End While

                    ' Wait for a second to give server time to put all the data onto the network stream.
                    Threading.Thread.Sleep(1000)

                    ' Read the response.
                    While Me.m_networkStream.DataAvailable

                        putResponseStream.WriteByte(Convert.ToByte(Me.m_networkStream.ReadByte()))

                    End While

                    putResponseStream.Seek(0, IO.SeekOrigin.Begin)

                Catch ex As Exception

                    Throw New ObjectExchangeException(ObjectExchangeErrorType.StreamError, OPPResources.Error_NetworkStream, ex)

                End Try

                Dim putResponse As New ObjectExchangeItem(putResponseStream)

                ' Take action based on the response sent by the Object Push Service.

                If putResponse.Opcode <> ObjectExchangeOpcode.Success AndAlso putResponse.Opcode <> _
                ObjectExchangeOpcode.SuccessFinal AndAlso putResponse.Opcode <> ObjectExchangeOpcode.Continued _
                AndAlso putResponse.Opcode <> ObjectExchangeOpcode.ContinueFinal Then

                    ' If response code is not Success or Continue, raise an error.
                    ObjectExchangeHelper.RaiseObjectExchangeError(putResponse.Opcode)

                End If
            Next

        End Sub

        ''' <summary>
        ''' Pulls the default business card of an OPP Service. 
        ''' </summary>
        ''' <param name="remoteCardName">Name of the business card received.
        ''' </param>
        ''' <param name="remoteCardContents">Byte stream of the business card
        ''' received.</param>
        ''' <param name="typeExpected">Should be of type Vcard as specified in 
        ''' the Type Verifier. Should be null if type does not have to be 
        ''' verified. </param>
        ''' <exception cref="ObjectExchangeException">The remote service denies
        ''' the pull request or an error occurs during the pull operation or an
        ''' attempt to access the network stream fails.</exception>
        ''' <exception cref="InvalidOperationException">NetworkStream is not set.</exception>
        Private Sub PullBusinessCardFromOPPService(ByRef remoteCardName As String, ByRef remoteCardContents() As Byte, _
                                                    ByVal typeExpected As String)

            If Me.m_networkStream Is Nothing Then
                Throw New InvalidOperationException(OPPResources.Error_NetworkStreamNotSet)
            End If

            remoteCardName = String.Empty

            remoteCardContents = New Byte() {}

            Dim receivedCardLength As UInteger = 0
            Dim remoteCardStream As New IO.MemoryStream()

            Dim cardWriter As New IO.BinaryWriter(remoteCardStream)
            Dim continueGet As Boolean = False

            Do

                ' Create the Get Default Object request.
                Dim getRequest As New ObjectExchangeItem(ObjectExchangeOpcode.GetFinal)

                If Not continueGet Then
                    getRequest.AddHeader(ObjectExchangeHeaderType.Name, String.Empty)
                    getRequest.AddHeader(ObjectExchangeHeaderType.Type, OPPResources.BusinessCardType)
                End If

                Dim getRequestBytes() As Byte = getRequest.GetByteStream()

                Dim getResponseStream As New IO.MemoryStream()

                Try

                    ' Send the Get request.
                    Me.m_networkStream.Write(getRequestBytes, 0, getRequestBytes.Length)

                    ' Wait for a response.
                    While Not Me.m_networkStream.DataAvailable
                        Continue While
                    End While

                    ' Wait for a second to give server time to put all the
                    ' data onto the network stream.
                    Threading.Thread.Sleep(1000)

                    ' Read the response.
                    While Me.m_networkStream.DataAvailable
                        getResponseStream.WriteByte(CByte(Me.m_networkStream.ReadByte()))
                    End While

                    getResponseStream.Seek(0, IO.SeekOrigin.Begin)

                Catch ex As Exception

                    Throw New ObjectExchangeException(ObjectExchangeErrorType.StreamError, OPPResources.Error_NetworkStream, ex)

                End Try

                Dim getResponse As New ObjectExchangeItem(getResponseStream)

                ' Take action based on the response sent by the Object
                ' Push Service.
                Select Case getResponse.Opcode

                    Case ObjectExchangeOpcode.Continued, ObjectExchangeOpcode.ContinueFinal

                        ' Get successful. More packets coming.
                        ' Get the name of the business card.
                        If getResponse.HeaderTypeExists(ObjectExchangeHeaderType.Name) Then
                            remoteCardName = Encoding.BigEndianUnicode.GetString(getResponse.GetHeader(ObjectExchangeHeaderType.Name))
                        End If

                        ' Get the length of the business card.
                        If getResponse.HeaderTypeExists(ObjectExchangeHeaderType.Length) Then
                            receivedCardLength = CUInt(ObjectExchangeHelper.FormatUInt32(BitConverter.ToUInt32(getResponse.GetHeader(ObjectExchangeHeaderType.Length), 0)))

                        End If

                        ' Get the contents of the business card.
                        If getResponse.HeaderTypeExists(ObjectExchangeHeaderType.Body) Then
                            cardWriter.Write(getResponse.GetHeader(ObjectExchangeHeaderType.Body))
                        End If

                        continueGet = True
                        Exit Select

                    Case ObjectExchangeOpcode.Success, ObjectExchangeOpcode.SuccessFinal

                        ' Get successful. Last packet.
                        ' Get the name of the business card.
                        If getResponse.HeaderTypeExists(ObjectExchangeHeaderType.Name) Then
                            remoteCardName = Encoding.BigEndianUnicode.GetString(getResponse.GetHeader(ObjectExchangeHeaderType.Name))
                        End If

                        ' Get the length of the business card.
                        If getResponse.HeaderTypeExists(ObjectExchangeHeaderType.Length) Then
                            receivedCardLength = CUInt(ObjectExchangeHelper.FormatUInt32(BitConverter.ToUInt32 _
                            (getResponse.GetHeader(ObjectExchangeHeaderType.Length), 0)))
                        End If

                        ' Get the contents of the business card.
                        If getResponse.HeaderTypeExists(ObjectExchangeHeaderType.EndOfBody) Then
                            cardWriter.Write(getResponse.GetHeader(ObjectExchangeHeaderType.EndOfBody))
                        End If

                        ' Get the card bytes.
                        remoteCardContents = remoteCardStream.ToArray()

                        If receivedCardLength > 0 AndAlso remoteCardContents.Length <> receivedCardLength Then
                            Throw New ObjectExchangeException(ObjectExchangeErrorType.StreamError, _
                            OPPResources.Error_FaultyBusinessCard)
                        End If

                        continueGet = False

                        Exit Select

                    Case Else

                        ObjectExchangeHelper.RaiseObjectExchangeError(getResponse.Opcode)
                        Exit Select

                End Select

            Loop While continueGet

            cardWriter.Close()

            If Not String.IsNullOrEmpty(typeExpected) Then

                ' Type has to be verified. Verify the card.
                m_typeVerifier.VerifyType(remoteCardContents, typeExpected)

            End If
        End Sub

        ''' <summary>
        ''' Pushes a business card and pulls the default business card of an 
        ''' OPP Service. 
        ''' </summary>
        ''' <param name="localCardName">Name of the business card to send to 
        ''' the OPP Service.</param>
        ''' <param name="localCardContents">Byte stream of the business card to
        ''' send to the OPP Service.</param>
        ''' <param name="remoteCardName">Name of the business card sent by the
        ''' OPP Service.</param>
        ''' <param name="remoteCardContents">Byte stream of the business card
        ''' sent by the OPP Service.</param>
        ''' <param name="typeExpected">Should be of type Vcard as specified in 
        ''' the Type Verifier. Should be null if type does not have to be 
        ''' verified. </param>
        ''' <exception cref="ObjectExchangeException">The remote service denies
        ''' the exchange request or an error occurs during the exchange 
        ''' operation or an attempt to access the network stream fails.
        ''' </exception>
        ''' <exception cref="InvalidOperationException">NetworkStream is not set.</exception>
        Private Sub ExchangeBusinessCardsWithOPPService(ByVal localCardName _
                    As String, ByVal localCardContents() As Byte, ByRef remoteCardName As String, _
                    ByRef remoteCardContents() As Byte, ByVal typeExpected As String)

            ' Push the object to the remote device.
            PushObjectToOPPService(localCardName, localCardContents)

            ' Pull the default business card of the remote device.
            PullBusinessCardFromOPPService(remoteCardName, remoteCardContents, typeExpected)

        End Sub


#End Region

#Region "Public Methods/Functions"

        ''' <summary>
        ''' Sends a Connect request to an OPP service.
        ''' </summary>
        ''' <exception cref="ObjectExchangeException">The remote service denies
        ''' the connect request or an error occurs during the connect operation
        ''' or an attempt to access the network stream fails.</exception>
        ''' <exception cref="InvalidOperationException">NetworkStream is not set.</exception>
        Public Sub Connect()

            With Me

                If .m_networkStream Is Nothing Then
                    Throw New InvalidOperationException(OPPResources.Error_NetworkStreamNotSet)
                End If

                ' Create the Connect request.
                Dim connectRequest As New ObjectExchangeItem(ObjectExchangeOpcode.Connect)

                ' Add an Obex Version Number field to the packet.
                connectRequest.AddHeader(ObjectExchangeHeaderType.ObexVersionNumber, New Byte() {16})

                ' Add a Flags field to the packet.
                connectRequest.AddHeader(ObjectExchangeHeaderType.Flags, New Byte() {0})

                ' Add a Maximum Obex Packet Length field to the packet.
                connectRequest.AddHeader(ObjectExchangeHeaderType.MaxObexPacketLength, New Byte() {32, 0})

                Dim connectRequestBytes() As Byte = connectRequest.GetByteStream()

                Dim connectResponseStream As New IO.MemoryStream()

                Try

                    ' Lock the network stream.
                    Threading.Monitor.Enter(.m_networkStream)

                    ' Send the Connect request.
                    .m_networkStream.Write(connectRequestBytes, 0, connectRequestBytes.Length)

                    ' Wait for a response.
                    While Not .m_networkStream.DataAvailable
                        Continue While
                    End While

                    ' Wait for a second to give server time to put all the data
                    ' onto the network stream.
                    Threading.Thread.Sleep(1000)

                    ' Read the response.
                    While .m_networkStream.DataAvailable
                        connectResponseStream.WriteByte(CByte(.m_networkStream.ReadByte()))
                    End While

                    connectResponseStream.Seek(0, IO.SeekOrigin.Begin)

                Catch ex As Exception

                    Throw New ObjectExchangeException(ObjectExchangeErrorType.StreamError, OPPResources.Error_NetworkStream, ex)

                Finally

                    ' Release the network stream.
                    Threading.Monitor.Exit(.m_networkStream)

                End Try

                Dim connectResponse As New ObjectExchangeItem(connectResponseStream)

                ' Take action based on the response sent by the Object Push 
                ' Service.
                If connectResponse.Opcode = ObjectExchangeOpcode.Success OrElse connectResponse.Opcode = _
                ObjectExchangeOpcode.SuccessFinal Then

                    ' Connection successful.
                    ' Decide the maximum length of an object exchange packet.
                    Dim remoteServiceMaxPacketSize As UShort = ObjectExchangeHelper.FormatUInt16(BitConverter.ToUInt16( _
                                                            connectResponse.GetHeader(ObjectExchangeHeaderType.MaxObexPacketLength), _
                                                            0))

                    If DEFAULT_MAX_PACKET_SIZE > remoteServiceMaxPacketSize Then

                        .maxPacketSize = remoteServiceMaxPacketSize

                    End If

                Else

                    ObjectExchangeHelper.RaiseObjectExchangeError(connectResponse.Opcode)

                End If

            End With

        End Sub

        ''' <summary>
        ''' Sends a Disconnect request to an OPP service.
        ''' </summary>
        ''' <exception cref="ObjectExchangeException">The remote service denies
        ''' the disconnect request or an error occurs during the disconnect 
        ''' operation or an attempt to access the network stream fails.
        ''' </exception>
        ''' <exception cref="InvalidOperationException">NetworkStream is not set.</exception>
        Public Sub Disconnect()

            With Me

                If .m_networkStream Is Nothing Then
                    Throw New InvalidOperationException(OPPResources.Error_NetworkStreamNotSet)
                End If

                ' Create the Disconnect request.
                Dim disconnectRequest As New ObjectExchangeItem(ObjectExchangeOpcode.Disconnect)

                Dim disconnectRequestBytes() As Byte = disconnectRequest.GetByteStream()
                Dim disconnectResponseStream As New IO.MemoryStream()

                Try

                    ' Lock the network stream.
                    Threading.Monitor.Enter(.m_networkStream)

                    ' Send the Disconnect request.
                    .m_networkStream.Write(disconnectRequestBytes, 0, disconnectRequestBytes.Length)

                    ' Wait for a response.
                    While Not .m_networkStream.DataAvailable

                        Continue While

                    End While

                    ' Wait for a second to give server time to put all the data 
                    ' onto the network stream.
                    Threading.Thread.Sleep(1000)

                    ' Read the response.
                    While Me.m_networkStream.DataAvailable
                        disconnectResponseStream.WriteByte(CByte(.m_networkStream.ReadByte()))
                    End While

                    disconnectResponseStream.Seek(0, IO.SeekOrigin.Begin)

                Catch ex As Exception

                    Throw New ObjectExchangeException(ObjectExchangeErrorType.StreamError, OPPResources.Error_NetworkStream, ex)

                Finally

                    ' Release the network stream.
                    Threading.Monitor.Exit(.m_networkStream)

                End Try

                Dim disconnectResponse As New ObjectExchangeItem(disconnectResponseStream)

                ' Take action based on the response sent by the Object Push Service.
                If disconnectResponse.Opcode <> ObjectExchangeOpcode.Success AndAlso _
                disconnectResponse.Opcode <> ObjectExchangeOpcode.SuccessFinal Then

                    ObjectExchangeHelper.RaiseObjectExchangeError(disconnectResponse.Opcode)

                End If

            End With

        End Sub

        ''' <summary>
        ''' Synchronous method that pushes a file over the stream to the remote
        ''' OPP Service.
        ''' </summary>
        ''' <param name="localFilePath">Path of the file to be pushed.</param>
        ''' <exception cref="ArgumentNullException">LocalFilePath is passed as
        ''' an empty string or a null reference.</exception>
        ''' <exception cref="ArgumentException">localFilePath specified does
        ''' not exist.</exception>
        ''' <exception cref="OPPException">An error occurs during the push
        ''' operation. (Note that the inner exception contains details of the
        ''' reason for exception)</exception>
        Public Sub PushObject(ByVal localFilePath As String)

            If String.IsNullOrEmpty(localFilePath) Then
                Throw New ArgumentNullException("localFilePath")
            End If

            If Not IO.File.Exists(localFilePath) Then
                Throw New ArgumentException(OPPResources.Error_FileDoesntExist, "localFilePath")
            End If

            PushObject(IO.Path.GetFileName(localFilePath), IO.File.ReadAllBytes(localFilePath))

        End Sub

        ''' <summary>
        ''' Synchronous method that pushes data over the stream to the remote 
        ''' OPP Service.
        ''' </summary>
        ''' <param name="localObjectName">Name of the object to be pushed.</param>
        ''' <param name="localObjectContent">The object to be pushed.</param>
        ''' <exception cref="ArgumentNullException">LocalObjectName is passed
        ''' as an empty string or a null reference or localObjectContent is
        ''' passed as a null reference.</exception>
        ''' <exception cref="OPPException">An error occurs during the push
        ''' operation. (Note that the inner exception contains details of the
        ''' reason for exception)</exception>
        Public Sub PushObject(ByVal localObjectName As String, ByVal localObjectContent() As Byte)

            If String.IsNullOrEmpty(localObjectName) Then
                Throw New ArgumentNullException("localObjectName")
            End If

            If localObjectContent Is Nothing Then
                Throw New ArgumentNullException("localObjectContent")
            End If

            ' Raise the ObjectPushBegin event. 
            RaiseEvent ObjectPushBegin(Me, New ObjectPushBeginEventArgs(localObjectName, Nothing))

            Dim exception As Exception = Nothing

            Try

                ' Lock the network stream.
                Threading.Monitor.Enter(Me.m_networkStream)

                ' Push the object to the remote device.
                PushObjectToOPPService(localObjectName, localObjectContent)

            Catch ex As Exception

                exception = New OPPException(OPPResources.Error_ObjectPush, ex)
                Throw exception

            Finally

                ' Release the network stream.
                Threading.Monitor.Exit(Me.m_networkStream)

                ' Raise the ObjectPushCompleted event. 
                RaiseEvent ObjectPushCompleted(Me, New ObjectPushCompletedEventArgs(localObjectName, exception, False, Nothing))

            End Try
        End Sub

        ''' <summary>
        ''' Asynchronous method that pushes a file over the stream to the remote
        ''' OPP Service.
        ''' </summary>
        ''' <param name="localFilePath">Path of the file to be pushed.</param>
        ''' <param name="userState">A unique object which can be used to keep 
        ''' track of the request.</param>
        ''' <exception cref="ArgumentNullException">LocalFilePath is passed as
        ''' an empty string or a null reference.</exception>
        ''' <exception cref="ArgumentException">LocalFilePath specified does
        ''' not exist.</exception>
        Public Sub PushObjectAsync(ByVal localFilePath As String, ByVal userState As Object)

            If String.IsNullOrEmpty(localFilePath) Then
                Throw New ArgumentNullException("localFilePath")
            End If

            If Not IO.File.Exists(localFilePath) Then
                Throw New ArgumentException(OPPResources.Error_FileDoesntExist, "localFilePath")
            End If

            ' Add the request to the queue.
            AddRequestToRequestQueue(New OPPRequest(OPPRequestType.ObjectPush, userState, IO.Path.GetFileName(localFilePath), _
                                                    IO.File.ReadAllBytes(localFilePath)))

        End Sub

        ''' <summary>
        ''' Asynchronous method that pushes data over the stream to the remote
        ''' OPP Service.
        ''' </summary>
        ''' <param name="localObjectName">Name of the object to be pushed.
        ''' </param>
        ''' <param name="localObjectContent">The object to be pushed.</param>
        ''' <param name="userState">A unique object which can be used to keep 
        ''' track of the request.</param>
        ''' <exception cref="ArgumentNullException">LocalObjectName is passed
        ''' as an empty string or a null reference or localObjectContent is
        ''' passed as a null reference.</exception>
        Public Sub PushObjectAsync(ByVal localObjectName As String, ByVal localObjectContent() As Byte, ByVal userState As Object)

            If String.IsNullOrEmpty(localObjectName) Then
                Throw New ArgumentNullException("localObjectName")
            End If

            If localObjectContent Is Nothing Then
                Throw New ArgumentNullException("localObjectContent")
            End If

            ' Add the request to the queue.
            AddRequestToRequestQueue(New OPPRequest(OPPRequestType.ObjectPush, userState, localObjectName, localObjectContent))

        End Sub

        ''' <summary>
        ''' Synchronous method that pulls the default business card of an OPP 
        ''' Service. 
        ''' </summary>
        ''' <param name="remoteCardName">Name of the business card received.
        ''' </param>
        ''' <param name="remoteCardContents">Byte stream of the business card
        ''' received.</param>
        ''' <exception cref="OPPException">An error occurs during the pull
        ''' operation. (Note that the inner exception contains details of the
        ''' reason for exception)</exception>
        Public Sub PullBusinessCard(ByRef remoteCardName As String, ByRef remoteCardContents() As Byte)

            ' Raise the BusinessCardPullBegin event. 
            RaiseEvent BusinessCardPullBegin(Me, New BusinessCardPullBeginEventArgs(Nothing))

            Dim exception As Exception = Nothing
            remoteCardName = String.Empty
            remoteCardContents = New Byte(-1) {}

            Try

                ' Lock the network stream.
                Threading.Monitor.Enter(Me.m_networkStream)

                ' Pull the default business card from the remote device.
                PullBusinessCardFromOPPService(remoteCardName, remoteCardContents, Nothing)

            Catch ex As Exception

                exception = New OPPException(OPPResources.Error_BusinessCardPull, ex)
                Throw exception

            Finally

                ' Release the network stream.
                Threading.Monitor.Exit(Me.m_networkStream)

                ' Raise the BusinessCardPullCompleted event. 
                RaiseEvent BusinessCardPullCompleted(Me, New BusinessCardPullCompletedEventArgs(remoteCardName, _
                                                                                                remoteCardContents, _
                                                                                                exception, False, Nothing))

            End Try

        End Sub

        ''' <summary>
        ''' Synchronous method that pulls the default business card of an OPP
        ''' Service and verifies that the card received is a valid card. Note
        ''' that the Type Verifier should be set. If a type mismatch occurs, 
        ''' OPPTypeMismatchException is thrown.
        ''' </summary>
        ''' <param name="remoteCardName">Name of the business card received.
        ''' </param>
        ''' <param name="remoteCardContents">Byte stream of the business card
        ''' received.</param>
        ''' <param name="typeExpected">Should be of type Vcard as specified in 
        ''' the Type Verifier.</param>
        ''' <exception cref="ArgumentNullException">TypeExpected is passed as 
        ''' an empty string or a null reference.</exception>
        ''' <exception cref="InvalidOperationException">TypeVerifier has not
        ''' been set.</exception>
        ''' <exception cref="ArgumentException">Type verification for 
        ''' typeExpected is not supported by the TypeVerifier.</exception>
        ''' <exception cref="OPPException">An error occurs during the pull
        ''' operation. (Note that the inner exception contains details of the
        ''' reason for exception)</exception>
        Public Sub PullBusinessCard(ByRef remoteCardName As String, ByRef remoteCardContents() As Byte, _
                                     ByVal typeExpected As String)

            If String.IsNullOrEmpty(typeExpected) Then
                Throw New ArgumentNullException("typeExpected")
            End If

            If m_typeVerifier Is Nothing Then
                Throw New InvalidOperationException(OPPResources.Error_VerifierNotSet)
            End If

            If Not m_typeVerifier.IsObjectTypeSupported(typeExpected) Then
                Throw New ArgumentException(OPPResources.Error_TypeNotSupported, "typeExpected")
            End If

            ' Raise the BusinessCardPullBegin event. 
            RaiseEvent BusinessCardPullBegin(Me, New BusinessCardPullBeginEventArgs(Nothing))

            Dim exception As Exception = Nothing
            remoteCardName = String.Empty
            remoteCardContents = New Byte(-1) {}

            Try

                ' Lock the network stream.
                Threading.Monitor.Enter(Me.m_networkStream)

                ' Pull the default business card from the remote device.
                PullBusinessCardFromOPPService(remoteCardName, remoteCardContents, typeExpected)

            Catch ex As Exception

                exception = New OPPException(OPPResources.Error_BusinessCardPull, ex)
                Throw exception

            Finally

                ' Release the network stream.
                Threading.Monitor.Exit(Me.m_networkStream)

                ' Raise the BusinessCardPullCompleted event. 
                RaiseEvent BusinessCardPullCompleted(Me, New BusinessCardPullCompletedEventArgs(remoteCardName, remoteCardContents, exception, False, Nothing))

            End Try

        End Sub

        ''' <summary>
        ''' Asynchronous method that pulls the default business card of an OPP
        ''' Service.
        ''' </summary>
        ''' <param name="userState">A unique object which can be used to keep
        ''' track of the request.</param>
        Public Sub PullBusinessCardAsync(ByVal userState As Object)

            ' Add the request to the queue.
            AddRequestToRequestQueue(New OPPRequest(OPPRequestType.BusinessCardPull, userState, String.Empty))

        End Sub

        ''' <summary>
        ''' Asynchronous method that pulls the default business card of an OPP
        ''' Service and verifies that the card received is a valid card. Note
        ''' that the Type Verifier should be set. If a type mismatch occurs,
        ''' OPPTypeMismatchException is thrown.
        ''' </summary>
        ''' <param name="userState">A unique object which can be used to keep 
        ''' track of the request.</param>
        ''' <param name="typeExpected">Should be of type Vcard as specified in
        ''' the Type Verifier.</param>
        ''' <exception cref="ArgumentNullException">TypeExpected is passed as 
        ''' an empty string or a null reference.</exception>
        ''' <exception cref="InvalidOperationException">TypeVerifier has not
        ''' been set.</exception>
        ''' <exception cref="ArgumentException">Type verification for 
        ''' typeExpected is not supported by the TypeVerifier.</exception>
        Public Sub PullBusinessCardAsync(ByVal userState As Object, ByVal typeExpected As String)

            If String.IsNullOrEmpty(typeExpected) Then
                Throw New ArgumentNullException("typeExpected")
            End If

            If m_typeVerifier Is Nothing Then
                Throw New InvalidOperationException(OPPResources.Error_VerifierNotSet)
            End If

            If Not m_typeVerifier.IsObjectTypeSupported(typeExpected) Then
                Throw New ArgumentException(OPPResources.Error_TypeNotSupported, "typeExpected")
            End If

            ' Add the request to the queue.
            AddRequestToRequestQueue(New OPPRequest(OPPRequestType.BusinessCardPullWithVerification, userState, typeExpected))

        End Sub

        ''' <summary>
        ''' Synchronous method that pushes a business card and pulls the 
        ''' default business card of an OPP Service. 
        ''' </summary>
        ''' <param name="localCardFilePath">Path of the business card file to
        ''' be pushed.</param>
        ''' <param name="remoteCardName">Name of the business card sent by the 
        ''' OPP Service.</param>
        ''' <param name="remoteCardContents">Byte stream of the business card
        ''' sent by the OPP Service.</param>
        ''' <exception cref="ArgumentNullException">localCardFilePath is passed 
        ''' as an empty string or a null reference.</exception>
        ''' <exception cref="ArgumentException">localCardFilePath specified does
        ''' not exist.</exception>
        ''' <exception cref="OPPException">An error occurs during the exchange
        ''' operation. (Note that the inner exception contains details of the
        ''' reason for exception)</exception>
        Public Sub ExchangeBusinessCards(ByVal localCardFilePath As String, ByRef remoteCardName As String, _
                                          ByRef remoteCardContents() As Byte)

            If String.IsNullOrEmpty(localCardFilePath) Then
                Throw New ArgumentNullException("localCardFilePath")
            End If

            If Not IO.File.Exists(localCardFilePath) Then
                Throw New ArgumentException(OPPResources.Error_FileDoesntExist, "localCardFilePath")
            End If

            ExchangeBusinessCards(IO.Path.GetFileName(localCardFilePath), IO.File.ReadAllBytes(localCardFilePath), _
                                  remoteCardName, remoteCardContents)

        End Sub

        ''' <summary>
        ''' Synchronous method that pushes a business card and pulls the default
        ''' business card of an OPP Service. 
        ''' </summary>
        ''' <param name="localCardName">Name of the business card to send to the
        ''' OPP Service.</param>
        ''' <param name="localCardContents">Byte stream of the business card to 
        ''' send to the OPP Service.</param>
        ''' <param name="remoteCardName">Name of the business card sent by the 
        ''' OPP Service.</param>
        ''' <param name="remoteCardContents">Byte stream of the business card 
        ''' sent by the OPP Service.</param>
        ''' <exception cref="ArgumentNullException">LocalCardName is passed
        ''' as an empty string or a null reference or localCardContents is
        ''' passed as a null reference.</exception>
        ''' <exception cref="OPPException">An error occurs during the exchange
        ''' operation. (Note that the inner exception contains details of the
        ''' reason for exception)</exception>
        Public Sub ExchangeBusinessCards(ByVal localCardName As String, ByVal localCardContents() As Byte, _
                                          ByRef remoteCardName As String, ByRef remoteCardContents() As Byte)

            If String.IsNullOrEmpty(localCardName) Then
                Throw New ArgumentNullException("localCardName")
            End If

            If localCardContents Is Nothing Then
                Throw New ArgumentNullException("localCardContents")
            End If

            ' Raise the BusinessCardExchangeBegin event. 
            RaiseEvent BusinessCardExchangeBegin(Me, New BusinessCardExchangeBeginEventArgs(localCardName, Nothing))

            Dim exception As Exception = Nothing
            remoteCardName = String.Empty
            remoteCardContents = New Byte(-1) {}

            Try

                ' Lock the network stream.
                Threading.Monitor.Enter(Me.m_networkStream)

                ' Exchange cards with the remote device.
                ExchangeBusinessCardsWithOPPService(localCardName, localCardContents, remoteCardName, remoteCardContents, Nothing)

            Catch ex As Exception

                exception = New OPPException(OPPResources.Error_BusinessCardExchange, ex)
                Throw exception

            Finally

                ' Release the network stream.
                Threading.Monitor.Exit(Me.m_networkStream)

                ' Raise the BusinessCardExchangeCompleted event. 
                RaiseEvent BusinessCardExchangeCompleted(Me, New BusinessCardExchangeCompletedEventArgs(localCardName, _
                                                                                                        remoteCardName, _
                                                                                                        remoteCardContents, _
                                                                                                        exception, False, Nothing))

            End Try

        End Sub

        ''' <summary>
        ''' Synchronous method that pushes a business card and pulls the 
        ''' default business card of an OPP Service and verifies that the card
        ''' received is a valid card. Note that the Type Verifier should be 
        ''' set. If a type mismatch occurs, OPPTypeMismatchException is thrown.
        ''' </summary>
        ''' <param name="localCardFilePath">Path of the business card file to 
        ''' be pushed.</param>
        ''' <param name="remoteCardName">Name of the business card sent by the
        ''' OPP Service.</param>
        ''' <param name="remoteCardContents">Byte stream of the business card 
        ''' sent by the OPP Service.</param>
        ''' <param name="typeExpected">Should be of type Vcard as specified in 
        ''' the Type Verifier.</param>
        ''' <exception cref="ArgumentNullException">LocalCardFilePath or 
        ''' typeExpected is passed as an empty string or a null reference.
        ''' </exception>
        ''' <exception cref="ArgumentException">LocalCardFilePath specified does
        ''' not exist or type verification for typeExpected is not supported by
        ''' the TypeVerifier.</exception>
        ''' <exception cref="InvalidOperationException">TypeVerifier has not
        ''' been set.</exception>
        ''' <exception cref="OPPException">An error occurs during the exchange
        ''' operation. (Note that the inner exception contains details of the
        ''' reason for exception)</exception>
        Public Sub ExchangeBusinessCards(ByVal localCardFilePath As String, ByRef remoteCardName As String, _
                                          ByRef remoteCardContents() As Byte, ByVal typeExpected As String)

            If String.IsNullOrEmpty(localCardFilePath) Then
                Throw New ArgumentNullException("localCardFilePath")
            End If

            If Not IO.File.Exists(localCardFilePath) Then
                Throw New ArgumentException(OPPResources.Error_FileDoesntExist, "localCardFilePath")
            End If

            ExchangeBusinessCards(IO.Path.GetFileName(localCardFilePath), IO.File.ReadAllBytes(localCardFilePath), _
                                  remoteCardName, remoteCardContents, typeExpected)

        End Sub

        ''' <summary>
        ''' Synchronous method that pushes a business card and pulls the default
        ''' business card of an OPP Service and verifies that the card received
        ''' is a valid card. Note that the Type Verifier should be set. If a 
        ''' type mismatch occurs, OPPTypeMismatchException is thrown.
        ''' </summary>
        ''' <param name="localCardName">Name of the business card to send to the
        ''' OPP Service.</param>
        ''' <param name="localCardContents">Byte stream of the business card to 
        ''' send to the OPP Service.</param>
        ''' <param name="remoteCardName">Name of the business card sent by the 
        ''' OPP Service.</param>
        ''' <param name="remoteCardContents">Byte stream of the business card 
        ''' sent by the OPP Service.</param>
        ''' <param name="typeExpected">Should be of type Vcard as specified in 
        ''' the Type Verifier.</param>
        ''' <exception cref="ArgumentNullException">LocalCardName or 
        ''' typeExpected is passed as an empty string or a null reference or
        ''' localCardContents is passed as a null reference.</exception>
        ''' <exception cref="InvalidOperationException">TypeVerifier has not
        ''' been set.</exception>
        ''' <exception cref="ArgumentException">Type verification for 
        ''' typeExpected is not supported by the TypeVerifier.</exception>
        ''' <exception cref="OPPException">An error occurs during the exchange
        ''' operation. (Note that the inner exception contains details of the
        ''' reason for exception)</exception>
        Public Sub ExchangeBusinessCards(ByVal localCardName As String, ByVal localCardContents() As Byte, _
                                          ByRef remoteCardName As String, ByRef remoteCardContents() As Byte, _
                                          ByVal typeExpected As String)

            If String.IsNullOrEmpty(localCardName) Then
                Throw New ArgumentNullException("localCardName")
            End If

            If localCardContents Is Nothing Then
                Throw New ArgumentNullException("localCardContents")
            End If

            If String.IsNullOrEmpty(typeExpected) Then
                Throw New ArgumentNullException("typeExpected")
            End If

            If m_typeVerifier Is Nothing Then
                Throw New InvalidOperationException(OPPResources.Error_VerifierNotSet)
            End If

            If Not m_typeVerifier.IsObjectTypeSupported(typeExpected) Then
                Throw New ArgumentException(OPPResources.Error_TypeNotSupported, "typeExpected")
            End If

            ' Raise the BusinessCardExchangeBegin event. 
            RaiseEvent BusinessCardExchangeBegin(Me, New BusinessCardExchangeBeginEventArgs(localCardName, Nothing))

            Dim exception As Exception = Nothing
            remoteCardName = String.Empty
            remoteCardContents = New Byte(-1) {}

            Try

                ' Lock the network stream.
                Threading.Monitor.Enter(Me.m_networkStream)

                ' Exchange cards with the remote device.
                ExchangeBusinessCardsWithOPPService(localCardName, localCardContents, remoteCardName, remoteCardContents, _
                                                    typeExpected)

            Catch ex As Exception

                exception = New OPPException(OPPResources.Error_BusinessCardExchange, ex)
                Throw exception

            Finally

                ' Release the network stream.
                Threading.Monitor.Exit(Me.m_networkStream)

                ' Raise the BusinessCardExchangeCompleted event. 
                RaiseEvent BusinessCardExchangeCompleted(Me, New BusinessCardExchangeCompletedEventArgs(localCardName, _
                                                                                                        remoteCardName, _
                                                                                                        remoteCardContents, _
                                                                                                        exception, False, Nothing))

            End Try

        End Sub

        ''' <summary>
        ''' Asynchronous method that pushes a business card and pulls the 
        ''' default business card of an OPP Service. 
        ''' </summary>
        ''' <param name="localCardFilePath">Path of the business card file to
        ''' be pushed.</param>
        ''' <param name="userState">A unique object which can be used to keep 
        ''' track of the request.</param>
        ''' <exception cref="ArgumentNullException">LocalCardFilePath is passed
        ''' as an empty string or a null reference.</exception>
        ''' <exception cref="ArgumentException">LocalCardFilePath specified does
        ''' not exist.</exception>
        Public Sub ExchangeBusinessCardsAsync(ByVal localCardFilePath As String, ByVal userState As Object)

            If String.IsNullOrEmpty(localCardFilePath) Then
                Throw New ArgumentNullException("localCardFilePath")
            End If

            If Not IO.File.Exists(localCardFilePath) Then
                Throw New ArgumentException(OPPResources.Error_FileDoesntExist, "localCardFilePath")
            End If

            ' Add the request to the queue.
            AddRequestToRequestQueue(New OPPRequest(OPPRequestType.BusinessCardExchange, userState, _
                                                    IO.Path.GetFileName(localCardFilePath), IO.File.ReadAllBytes(localCardFilePath), _
                                                    String.Empty))

        End Sub

        ''' <summary>
        ''' Asynchronous method that pushes a business card and pulls the 
        ''' default business card of an OPP Service. 
        ''' </summary>
        ''' <param name="localCardName">Name of the business card to send to the
        ''' OPP Service.</param>
        ''' <param name="localCardContents">Byte stream of the business card to 
        ''' send to the OPP Service.</param>
        ''' <param name="userState">A unique object which can be used to keep 
        ''' track of the request.</param>
        ''' <exception cref="ArgumentNullException">LocalCardName is passed
        ''' as an empty string or a null reference or localCardContents is
        ''' passed as a null reference.</exception>
        Public Sub ExchangeBusinessCardsAsync(ByVal localCardName As String, ByVal localCardContents() As Byte, _
                                               ByVal userState As Object)

            If String.IsNullOrEmpty(localCardName) Then
                Throw New ArgumentNullException("localCardName")
            End If

            If localCardContents Is Nothing Then
                Throw New ArgumentNullException("localCardContents")
            End If

            ' Add the request to the queue.
            AddRequestToRequestQueue(New OPPRequest(OPPRequestType.BusinessCardExchange, userState, localCardName, localCardContents, _
                                                    String.Empty))

        End Sub

        ''' <summary>
        ''' Asynchronous method that pushes a business card and pulls the 
        ''' default business card of an OPP Service and verifies that the card 
        ''' received is a valid card. Note that the Type Verifier should be 
        ''' set. If a type mismatch occurs, OPPTypeMismatchException is thrown.
        ''' </summary>
        ''' <param name="localCardFilePath">Path of the business card file to be
        ''' pushed.</param>
        ''' <param name="typeExpected">Should be of type Vcard as specified in
        ''' the Type Verifier.</param>
        ''' <param name="userState">A unique object which can be used to keep 
        ''' track of the request.</param>
        ''' <exception cref="ArgumentNullException">localCardFilePath or 
        ''' typeExpected is passed as an empty string or a null reference.
        ''' </exception>
        ''' <exception cref="ArgumentException">LocalCardFilePath specified does
        ''' not exist or type verification for typeExpected is not supported by
        ''' the TypeVerifier.</exception>
        ''' <exception cref="InvalidOperationException">TypeVerifier has not
        ''' been set.</exception>
        Public Sub ExchangeBusinessCardsAsync(ByVal localCardFilePath As String, ByVal typeExpected As String, ByVal userState As Object)

            If String.IsNullOrEmpty(localCardFilePath) Then
                Throw New ArgumentNullException("localCardFilePath")
            End If

            If Not IO.File.Exists(localCardFilePath) Then
                Throw New ArgumentException(OPPResources.Error_FileDoesntExist, "localCardFilePath")
            End If

            If String.IsNullOrEmpty(typeExpected) Then
                Throw New ArgumentNullException("typeExpected")
            End If

            If m_typeVerifier Is Nothing Then
                Throw New InvalidOperationException(OPPResources.Error_VerifierNotSet)
            End If

            If Not m_typeVerifier.IsObjectTypeSupported(typeExpected) Then
                Throw New ArgumentException(OPPResources.Error_TypeNotSupported, "typeExpected")
            End If

            ' Add the request to the queue.
            AddRequestToRequestQueue(New OPPRequest(OPPRequestType.BusinessCardExchangeWithVerification, userState, _
                                                    IO.Path.GetFileName(localCardFilePath), IO.File.ReadAllBytes(localCardFilePath), _
                                                    typeExpected))

        End Sub

        ''' <summary>
        ''' Asynchronous method that pushes a business card and pulls the 
        ''' default business card of an OPP Service and verifies that the card
        ''' received is a valid card. Note that the Type Verifier should be set.
        ''' If a type mismatch occurs, OPPTypeMismatchException is thrown.
        ''' </summary>
        ''' <param name="localCardName">Name of the business card to send to 
        ''' the OPP Service.</param>
        ''' <param name="localCardContents">Byte stream of the business card
        ''' to send to the OPP Service.</param>
        ''' <param name="typeExpected">Should be of type Vcard as specified in 
        ''' the Type Verifier.</param>
        ''' <param name="userState">A unique object which can be used to keep 
        ''' track of the request.</param>
        ''' <exception cref="ArgumentNullException">LocalCardName or 
        ''' typeExpected is passed as an empty string or a null reference or
        ''' localCardContents is passed as a null reference.</exception>
        ''' <exception cref="InvalidOperationException">TypeVerifier has not
        ''' been set.</exception>
        ''' <exception cref="ArgumentException">Type verification for 
        ''' typeExpected is not supported by the TypeVerifier.</exception>
        Public Sub ExchangeBusinessCardsAsync(ByVal localCardName As String, ByVal localCardContents() As Byte, _
                                               ByVal typeExpected As String, ByVal userState As Object)

            If String.IsNullOrEmpty(localCardName) Then
                Throw New ArgumentNullException("localCardName")
            End If

            If localCardContents Is Nothing Then
                Throw New ArgumentNullException("localCardContents")
            End If

            If String.IsNullOrEmpty(typeExpected) Then
                Throw New ArgumentNullException("typeExpected")
            End If

            If m_typeVerifier Is Nothing Then
                Throw New InvalidOperationException(OPPResources.Error_VerifierNotSet)
            End If

            If Not m_typeVerifier.IsObjectTypeSupported(typeExpected) Then
                Throw New ArgumentException(OPPResources.Error_TypeNotSupported, "typeExpected")
            End If

            ' Add the request to the queue.
            AddRequestToRequestQueue(New OPPRequest(OPPRequestType.BusinessCardExchangeWithVerification, userState, _
                                                    localCardName, localCardContents, typeExpected))

        End Sub

        ''' <summary>
        ''' Cancel an asynchronous operation.
        ''' Throws an InvalidOperationException if the userState supplied is
        ''' invalid or the operation has already completed.
        ''' </summary>
        ''' <param name="userState">The user-supplied state object supplied 
        ''' when the asynchronous operation was requested.</param>
        ''' <exception cref="ArgumentNullException">userState is passed as a
        ''' null reference.</exception>
        ''' <exception cref="InvalidOperationException">userState is invalid
        ''' or the operation has already completed.</exception>
        Public Sub CancelAsync(ByVal userState As Object)

            If userState Is Nothing Then
                Throw New ArgumentNullException("userState")
            End If

            Dim invalidCancel As Boolean = True

            With Me

                ' Check whether this operation is currently executing.
                If .asyncOperationHandler.IsBusy AndAlso .runningAsyncRequest IsNot Nothing AndAlso _
                .runningAsyncRequest.UserState.Equals(userState) Then

                    .asyncOperationHandler.CancelAsync()
                    invalidCancel = False

                Else

                    Threading.Monitor.Enter(.requestQueue)

                    ' Search for the operation in the queue.
                    For Each request As OPPRequest In .requestQueue

                        If request.UserState.Equals(userState) Then

                            .requestQueue.Remove(request)
                            invalidCancel = False
                            Exit For

                        End If

                    Next

                    Threading.Monitor.Exit(.requestQueue)

                End If

            End With

            ' The userState was not found.
            If invalidCancel Then
                Throw New InvalidOperationException(OPPResources.Error_InvalidOrNotFoundUserState)
            End If

        End Sub

#End Region

    End Class
End Namespace
